<?php
/**
 * Router that maps controller/actions to classes
 * 
 * @package	Nuclide-Mvc
 * @version $Id$
 */

Zend_Loader::loadClass('Zend_Controller_Router_Abstract');
Zend_Loader::loadClass('Zend_Controller_Router_Route');
Zend_Loader::loadClass('Zend_Controller_Router_Rewrite');

/**
 * Router that maps controller/actions to classes
 * 
 * @package	Nuclide-Mvc
 */
class nuc_Controller_Router extends Zend_Controller_Router_Abstract
{
	/**
	 * Route collection
	 * 
	 * @var array
	 */
	protected $_routes = array();
	/**
	 * Latest route
	 * 
	 * @var string
	 */
	protected $_currentRoute = false;
	
    /**
     * Extension to default routing
     * 
     * @throws	Zend_Controller_Router_Exception
     * @return	Zend_Controller_Request_Abstract	Request object
     */
    public function route( Zend_Controller_Request_Abstract $request ) {
        if( !$request instanceof Zend_Controller_Request_Http ) {
			throw new Zend_Controller_Router_Exception(
				'Zend_Controller_Router_Rewrite requires a Zend_Controller_Request_Http-based request object'
			);
        }

        $pathInfo = $request->getPathInfo();

        // Find the matching route
        foreach( array_reverse( $this->_routes ) as $name => $route ) {
			$regex = isset( $route['validate'] ) ? $route['validate'] : array();
			unset( $route['validate'] );

        	$route = new Zend_Controller_Router_Route( $name, $route, $regex );
                    	       	
            if( $params = $route->match( $pathInfo ) ) {
        		foreach( $params as $param => $value ) {
					$request->setParam( $param, $value );

					if( $param === $request->getModuleKey() ) {
						$request->setModuleName( $value );
					}

					if( $param === $request->getControllerKey() ) {
						$request->setControllerName( $value );
					}
					
					if( $param === $request->getActionKey() ) {
						$request->setActionName( $value );
					}
				}
				
                $this->_currentRoute = $name;
                break;
            }
        }

		return $request;
    }
    
    /**
     * Generates a URL path that can be used in URL creation, redirection, etc. 
     * 
     * May be passed user params to override ones from URI, Request or even defaults. 
     * If passed parameter has a value of null, it's URL variable will be reset to
     * default. 
     * 
     * If null is passed as a route name assemble will use the current Route or 'default'
     * if current is not yet set.
     * 
     * Reset is used to signal that all parameters should be reset to it's defaults. 
     * Ignoring all URL specified values. User specified params still get precedence.
     * 
     * Encode tells to url encode resulting path parts.     
     *
     * @param  array $userParams Options passed by a user used to override parameters
     * @param  mixed $name The name of a Route to use
     * @param  bool $reset Whether to reset to the route defaults ignoring URL params
     * @param  bool $encode Tells to encode URL parts on output
     * @throws Zend_Controller_Router_Exception
     * @return string Resulting URL path
     */    
    public function assemble( $userParams, $name = null, $reset = false, $encode = true ) {
        if( $name == null ) {
            try {
                $name = $this->getCurrentRouteName();
            } catch( Zend_Controller_Router_Exception $e ) {
                $name = 'default';
            }
        }
        
        $route = $this->getRoute( $name );
        $url   = $route->assemble( $userParams, $reset, $encode );

        if( !preg_match( '|^[a-z]+://|', $url ) ) {
            $url = rtrim( $this->getFrontController()->getBaseUrl(), '/' ) . '/' . $url;
        }

        return $url;
    }
 
    /**
     * Retrieve a name of currently matched route
     *
     * @throws Zend_Controller_Router_Exception
     * @return Zend_Controller_Router_Route_Interface Route object
     */
    public function getCurrentRouteName()
    {
        if (!isset($this->_currentRoute)) {
            require_once 'Zend/Controller/Router/Exception.php';
            throw new Zend_Controller_Router_Exception("Current route is not defined");
        }
        return $this->_currentRoute;
    }	
    
    /**
     * Retrieve a named route
     *
     * @param string Name of the route
     * @throws Zend_Controller_Router_Exception
     * @return Zend_Controller_Router_Route_Interface Route object
     */
    public function getRoute( $name ) {
    	return new Zend_Controller_Router_Route( $name, $this->_routes[ $name ] );
    }
    
    /**
     * Class route registration
     *
     * @param	string	Route path
     * @param	string	Controller name
     * @param	string	Action name
     * @param	string	OPTIONAL Module name
     * @param	array	OPTIONAL Validation information
     */
	public function registerClassToRoute( $path, $controller, $action, $module = null, array $validate = array() ) {
		$this->_routes[ $path ] = array(
			'controller'	=> $controller,
			'action'		=> $action,
			'validate'		=> $validate
		);
		
		if( !is_null( $module ) ) {
			$this->_routes[ $path ]['module'] = $module;
		}
	}
}

/**
 * Exception for nuc_Controller_Route
 * 
 * @package		Nuclide-Mvc
 * @subpackage	Exceptions
 */
class nuc_Controller_RouteException extends Exception {}
?>